//
//  AABB.hpp
//  RayTracer
//
//  Created by 俞云康 on 3/3/16.
//  Copyright © 2016 yushroom. All rights reserved.
//

#ifndef AABB_hpp
#define AABB_hpp

#include "Math.hpp"
#include "Ray.hpp"

//constexpr float FLOAT_MAX = std::numeric_limits<float>::max();
//constexpr float FLOAT_MIN = std::numeric_limits<float>::lowest();

struct AABB {
    Position pmin{FLT_MAX, FLT_MAX, FLT_MAX};
    Position pmax{-FLT_MAX, -FLT_MAX, -FLT_MAX};
    
    AABB() {}
    AABB(const Position& p) : pmin(p), pmax(p) { }
    AABB(const Position& p1, const Position& p2) {
        pmin = Position(std::min(p1.x, p2.x), std::min(p1.y, p2.y), std::min(p1.z, p2.z));
        pmax = Position(std::max(p1.x, p2.x), std::max(p1.y, p2.y), std::max(p1.z, p2.z));
    }
    
    /// Expand the bounding box to contain another point
    inline void expandBy(const Position& p) {
        for (int i = 0; i < 3; ++i) {
            pmin[i] = std::min(pmin[i], p[i]);
            pmax[i] = std::max(pmax[i], p[i]);
        }
    }
    
    /// Expand the bounding box to contain another bounding box
    inline void expandBy(const AABB& b) {
        for (int i = 0; i < 3; ++i) {
            pmin[i] = std::min(pmin[i], b.pmin[i]);
            pmax[i] = std::max(pmax[i], b.pmax[i]);
        }
    }
    
    inline bool intersect(const Ray& ray, float *hitt0, float *hitt1) const {
        float t0 = 0, t1 = ray.maxt;
        for (int i = 0; i < 3; ++i) {
            float invRayDir = 1 / ray.dir[i];
            float tNear = (pmin[i] - ray.o[i]) * invRayDir;
            float tFar  = (pmax[i] - ray.o[i]) * invRayDir;
            
            if (tNear > tFar) std::swap(tNear, tFar);
            tFar *= 1 + 2 * gamma(3);
            t0 = std::max(t0, tNear);
            t1 = std::min(t1, tFar);
            if (t0 > t1) return false;
        }
        if (hitt0 != nullptr) *hitt0 = t0;
        if (hitt1 != nullptr) *hitt1 = t1;
        return true;
    }
    
    inline Position getCorner(int idx) const {
        Assert(idx >= 0 && idx < 8);
        float3 center = (pmax + pmin) / 2.f;
        float3 delta  = (pmax - pmin) / 2.f;
        int offset[] = { 1,  1, 1,  -1, 1,  1,  1, -1,  1,  1,  1, -1,
                        -1, -1, 1,  -1, 1, -1,  1, -1, -1, -1, -1, -1};
        int* o = offset+ idx*3;
        return Position(center.x+o[0]*delta.x, center.y+o[1]*delta.y, center.z+o[2]*delta.z);
    }
    
    inline bool inside(const Position& p) const {
        return
        p.x >= pmin.x &&
        p.x <= pmax.x &&
        p.y >= pmin.y &&
        p.y <= pmax.y &&
        p.z >= pmin.z &&
        p.z <= pmax.z;
    }
    
    inline Direction diagonal () const {
        return pmax - pmin;
    }
    
    inline float surfaceArea() const {
        Direction d = diagonal();
        return 2 * (d.x*d.y + d.y*d.z + d.x*d.z);
    }
    
    inline float volume() const {
        Direction d = diagonal();
        return d.x * d.y * d.z;
    }
    
    inline int maximumExtent() const {
        Direction d = diagonal();
        if (d.x > d.y && d.x > d.z)
            return 0;
        else if (d.y > d.z)
            return 1;
        else
            return 2;
    }
    
    inline void boundingSphere(Position* center, float *radius) const {
        *center = (pmin + pmax) / 2;
        *radius = inside(*center) ? Distance(*center, pmax) : 0;
    }
};

#endif /* AABB_hpp */
